package com.insightfullogic.honest_profiler.ports.javafx.util;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.CONTENT_LABEL_EXCEPTION;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.HEADER_DIALOG_ERR_EXPORTPROFILE;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.MESSAGE_DIALOG_ERR_EXPORTPROFILE;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.TITLE_DIALOG_ERR_EXPORTPROFILE;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.TITLE_DIALOG_OPENFILE;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.TYPE_FILE_ALL;
import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.TYPE_FILE_HP;
import static javafx.scene.control.Alert.AlertType.ERROR;
import static javafx.scene.layout.Priority.ALWAYS;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.function.Consumer;
import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext;
import javafx.scene.control.Alert;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Window;
/**
* Utility class for generating various {@link Dialog}s.
* <p>
* Whenever a {@link File} is selected (for reading or export) by one of the methods in the class, the parent directory
* is cached so that subsequent invocations of methods involving file selection start from the currently cached
* directory. This generally avoids a lot of repetitive file system navigation on the part of the user when working with
* multiple profiles or exporting a lot of information.
* <p>
* This concept could be extended a bit by keeping a mapping the type of file operation (open profile, export data) to
* separate cached directory, and by providing preferences in the application for preferred locations.
*/
public final class DialogUtil
{
// Class Properties
private static File CACHED_PARENT_DIR;
// Class Methods
/**
* Present a {@link Dialog} to the user which allows the selection of a log file, and return the selected
* {@link File}, if any.
* <p>
* The initial directory is the current working directory, if this is the first invocation, or cached directory.
* When a {@link File} is selected, its parent directory is cached.
* <p>
* The rationale behind this is that the directory from which the profiler front-end is started often may be "far
* away" from the directory where the profiler log files are stored. Also it is quite likely that log files are
* stored "close to each other" on the file system.
* <p>
*
* @param appCtx the {@link ApplicationContext} for the application
* @return the selected {@link File}
*/
public static File selectLogFile(ApplicationContext appCtx)
{
FileChooser fileChooser = new FileChooser();
fileChooser.setInitialDirectory(CACHED_PARENT_DIR);
// Initially show only .hpl files, but support random file extensions too, just in case.
fileChooser.getExtensionFilters().addAll(
new ExtensionFilter(appCtx.textFor(TYPE_FILE_HP), "*.hpl"),
new ExtensionFilter(appCtx.textFor(TYPE_FILE_ALL), "*.*"));
fileChooser.setSelectedExtensionFilter(fileChooser.getExtensionFilters().get(0));
fileChooser.setTitle(appCtx.textFor(TITLE_DIALOG_OPENFILE));
File file = fileChooser.showOpenDialog(null);
if (file != null)
{
CACHED_PARENT_DIR = file.getParentFile();
}
return file;
}
/**
* Present a {@link Dialog} to the user which allows the selection of a {@link File} into which data will be
* exported, and write the data to the {@link File}.
* <p>
* The initial directory is the current working directory, if this is the first invocation, or cached directory.
* When a {@link File} is selected, its parent directory is cached.
* <p>
* The rationale behind this is that the directory from which the profiler front-end is started often may be "far
* away" from the directory where the profiler log files are stored. Also it is quite likely that log files are
* stored "close to each other" on the file system.
* <p>
*
* @param appCtx the {@link ApplicationContext} for the application
* @param window the containing {@link Window}
* @param initialFileName the filename initially proposed in the filename input text input
* @param exportMethod the method which writes the exported data to the {@link File}
*/
public static void showExportDialog(ApplicationContext appCtx, Window window,
String initialFileName, Consumer<PrintWriter> exportMethod)
{
FileChooser chooser = new FileChooser();
chooser.setInitialDirectory(CACHED_PARENT_DIR);
chooser.setInitialFileName(initialFileName);
File file = chooser.showSaveDialog(window);
if (file != null)
{
CACHED_PARENT_DIR = file.getParentFile();
try (PrintWriter out = new PrintWriter(file, "UTF-8"))
{
exportMethod.accept(out);
out.flush();
}
catch (IOException ioe)
{
showExceptionDialog(
appCtx,
appCtx.textFor(TITLE_DIALOG_ERR_EXPORTPROFILE),
appCtx.textFor(HEADER_DIALOG_ERR_EXPORTPROFILE),
appCtx.textFor(MESSAGE_DIALOG_ERR_EXPORTPROFILE, file.getAbsolutePath()),
ioe);
}
}
}
/**
* Show an error {@link Dialog} with the specified properties.
* <p>
*
* @param title the title of the {@link Dialog}
* @param header the header of the {@link Dialog}
* @param content the content of the {@link Dialog}
*/
public static void showErrorDialog(String title, String header, String content)
{
Alert alert = new Alert(ERROR);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
alert.showAndWait();
}
/**
* Shows an exception {@link Dialog} with the specified properties. This is an error {@link Dialog} which contains
* the stack trace of the {@link Throwable} as expandable content.
* <p>
*
* @param appCtx the {@link ApplicationContext} for the application
* @param title the title of the {@link Dialog}
* @param header the header of the {@link Dialog}
* @param content the content of the {@link Dialog}
* @param t the {@link Throwable} whose stacktrace will be included in the {@link Dialog}
*/
public static void showExceptionDialog(ApplicationContext appCtx, String title, String header,
String content, Throwable t)
{
Alert alert = new Alert(ERROR);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
String exceptionText = null;
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw))
{
t.printStackTrace(pw);
exceptionText = sw.toString();
}
catch (IOException ioe)
{
// Ignore
}
Label label = new Label(appCtx.textFor(CONTENT_LABEL_EXCEPTION));
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, ALWAYS);
GridPane.setHgrow(textArea, ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
// Set expandable Exception into the dialog pane.
alert.getDialogPane().setExpandableContent(expContent);
alert.showAndWait();
}
// Instance Constructors
/**
* Empty Constructor for utility class.
*/
private DialogUtil()
{
// Empty Constructor for utility class
}
}